Translated by visionNoob, KNU https://github.com/insurgent92/CS231N_17_KOR_SUB
12시가 지났으니 진행하도록 하겠습니다.
이번 8강에서는 Deep learning software에 대해 알아볼 것입니다.
매년 아주 많은 변화가 생기는 정말 재미있는 주제입니다.
다만 매년 너무 많이 바뀌어서 매번 강의준비가 좀 빡세긴 합니다.
우선 몇가지 공지사항을 전달하겠습니다.
우선 지난 화요일에 프로젝트 기획서 제출기한이 마감되었습니다.
다들 좋은 아이디어를 가지고 기한내에 잘 제출했길 바랍니다.
현재 프로젝트 분야에 맞는 적절한 TA를 배치중에 있습니다.
추후에 더 많은 정보가 공지될 것입니다.
그리고 현재 과제1을 채점중에 있습니다.
가능한 빨리 점수를 알려드리도록 하겠습니다.
그리고 과제2의 제출기한도 얼마 남지 않았습니다. 다음 주 까지 라는 것을 꼭 명심하시기 바랍니다.
과제2를 진행할 때 반드시 Google Cloud를 잘 종료시켜서 각자 크래딧을 아끼도록 하세요
그리고 다시한번 말씀드리고 싶은 것은 과제2에서 GPU를 사용한만한 것은 마지막 문제뿐이 없다는 것입니다.
나머지 문제들은 python과 numpy만 사용하기 때문에 GPU를 쓸 일이 없습니다.
GPU는 정말 필요할 때만 사용하셔서 크래딧을 아껴 쓰시기 바랍니다.
마지막으로 이제 곧 중간고사가 예정되어 있습니다.
벌써 중간고사 기간이라는 것이 믿어지지 않지만 중간고사는 5월 9일 화요일 수업시간에 진행됩니다.
중간고사는 이론중심으로 출제될 것입니다.
지금까지 수업을 잘 이해하고 있는지를 확인하기 위한 몇가지 이론적인 문제들을 출제할 것입니다.
몇 가지 예상문제를 올려놓도록 하겠습니다.
질문 있나요?
[학생이 질문]
오픈북이냐고 질문하셨는데요 오픈북은 아닙니다.
지금까지 항상 클로즈북으로 진행해 왔습니다.
여러분이 수업에서 다룬 것들의 직관들을 얼마나 이해했는지를 확인하는 것 위주로 출제될 것입니다.
자 그럼 지난시간의 내용을 간단하게 복습해 보도록 하겠습니다.
지난 시간에 다양한 딥러닝 최적화 알고리즘을 배웠습니다.
SGD, Momentum, Nesterov, RMSProp 그리고 Adam 에 대해서 배웠습니다.
이 방법들을 모두 기본적인 SGD를 조금씩 변형시킨 방법이었죠 구현 자체는 간단하나 네트워크의 수렴속도는 더 빨랐습니다.
그리고 regularization에 대해서도 배웠습니다. 특히나 Dropout에 관한 것이었죠
Dropout은 forward pass에서 임의의 부분은 0으로 만들고 test time에서는 그 noise를 marginalize out 했습니다.
그리고 딥러닝에서 사용하는 다양한 regularization들의 일반적인 패턴에 대해서는 배웠습니다.
Train time에 noise를 추가하고, Test time에는 marginalize out 합니다.
그리고 Transfer learning도 배웠습니다. Pre-train model을 다운받아서 fine-tune 하는 방법이죠
Transfer learning을 이용하면 데이터셋이 많지 않더라도 딥러닝 문제를 다룰 수 있는 방법이었습니다.
이번 시간에는 화제를 조금 돌려서 소프트웨어/하드웨어가 동작하는 방식에 대해서 조금 다배워보도록 하겠습니다.
그리고 실제 여러분이 학습 시 사용하는 소프트웨어들을 조금 더 심도깊게 다뤄보도록 하겠습니다.
정리하자면 CPU와 GPU에 대해 조금 배울 것이고, 요즘 사람들이 가장 많이 사용하는 립러닝 프레임워크들에 대해서도 배울 것입니다.
자 우선 이런 얘기는 수도 없이 많이 들으셨을 것입니다. 컴퓨터에 CPU와 GPU가 있다는 것 말입니다.
딥러닝에서 GPU를 사용하는데, 사실 지금까지 왜 GPU가 CPU가 좋은지 에 대해서 명확하게 언급하지 않고 넘어갔습니다.
컴퓨터를 조립해본 적이 있나요? 손 한번 들어보시겠습니까
한 1/3에서 절반정도는 계시는군요
이 사진은 제 컴퓨터입니다. 제가 직접 조립했죠
컴퓨터 내부에서 많은 부품들이 있습니다. 아마 여러분들은 각각이 무엇인지 아실 거라고 믿습니다.
CPU는 Central Processing Unit이죠
이 조그마한 칩은 쿨러 믿에 숨어있습니다.
CPU는 엄청 작습니다. 공간을 크게 잡아먹지 않죠
자 여기 GPU들을 보면 공간을 엄청 차지하고 있습니다. 괴물같은 녀석들입니다.
GPU는 자기만의 쿨러가 있고 파워도 엄청 먹고 엄청 큽니다.
GPU가 얼마나 많은 파워를 사용하는지 그리고 얼마나 큰지는 케이스에서 얼마나 많은 부분을 차지하는지만 봐도 알 수 있습니다.
GPU란 것이 무엇이길래 딥러닝에서 이렇게 중요한 걸까요
GPU는 graphics card 혹은 Graphics Processing Unit 이라고 합니다.
사실 GPU는 computer graphics를 랜더링하기 위해서 만들어졌습니다. 게임같은 것들을 위해서죠
자 다시한번 손을 들어보죠, 여기에 집에서 컴퓨터게임을 하시는 분들이 계십니까?
절반정도 있군요 좋습니다.
게임을 하시거나 컴퓨터를 직접 조립해 보신 분들이라면 다음과 같은 질문에 대한 나름대로의 의견을 가지고 계실 것입니다.
[웃음]
이는 Computer Science에서 가장 큰 논쟁 중 하나입니다.
Intel vs AMD 라던가, 그래픽카드의 경우 NVIDIA vs AMD 같은 논쟁들이죠
텍스트 에디터의 경우 Vim vs Emacs도 있겠군요
대부분의 게이머들은 이 논쟁에 대해서 자기만의 의견을 가지고 있습니다.
딥러닝의 경우는 대부분 한쪽만 선택합니다. NVIDIA죠
만약 AMD 카드를 가지고있다면 아마 딥러닝에 사용하는데 문제가 많을 것입니다.
지난 수년간 NVIDIA는 딥러닝에 많은 공을 들여왔습니다.
NVIDIA의 전략은 세간의 주목을 받았습니다.
Nvidia의 많은 엔지니어들은 딥러닝에 적합한 하드웨어를 만들기 위해서 많은 노력을 했습니다.
그래서 딥러닝과 관련해서는 NVIDIA GPU가 거의 독점적으로 언급됩니다.
아마 미래에는 또 다른 후발주자가 생겨날 지 모르겠지만 적어도 지금은 NVIDIA가 독점적입니다.
자 그럼 CPU와 GPU의 차이는 무엇일까요? 제가 아주 간단하게 표로 한번 정리해 봤습니다.
위에 두개는 Intel의 최신 상업용 CPU들이고 밑에 두개는 NVIDIA의 최신 상업용 GPU들입니다.
여기에는 몇 가지 주요 트렌드가 있습니다.
GPU와 CPU모두 임의의 명령어를 수행할 수 있는 범용 컴퓨팅 머신입니다.
하지만 이 둘은 질적으로 아주 다릅니다.
CPU의 경우 core의 수가 적습니다. 오늘날의 상업용 테스크탑 CPU의 경우에 코어가 4개에서 6개 조금 많으면 10개 정도입니다.
그리고 hyperthreading 기술와 더불어 CPU는 8 ~ 20개의 스레드를 동시에 실행시킬 수 있습니다.
따라서 CPU는 한번에 20가지의 일(스레드)을 할 수 있는 것입니다.
이 숫자(20)가 그닥 커보이진 않습니다. 하지만 CPU의 멀티스레드는 아주 강력합니다.
아주 많은 일을 할 수 있으면 엄청 빠릅니다.
모든 CPU 명령어들은 정말 많은 일을 할 수 있죠 그리고 아주 독립적으로 수행합니다.
하지만 GPU의 경우는 상황이 조금 다릅니다. 고성능의 상업 GPU의 경우 수천개의 코어가 있습니다.
NVIDIA Titan XP와 같은 최상위 모델의 경우 3840개의 코어가 있습니다. 정말 엄청난 수 입니다.
동일 가격의 CPU를 보면 코어가 10개 뿐이 없죠
하지만 GPU의 단점을 보자면 각각의 코어가 더 느린 clock speed에서 동작한다는 점입니다.
그리고 두번째는 그 코어들이 그렇게 많은 일을 할 수 없다는 것입니다.
사실 CPU와 GPU코어는 1:1로 비교할 수 없습니다.
GPU코어들은 독립적으로 동작하지 않습니다.
코어마다 독립적인 테스크가 있는 것이 아니라 많은 코어들이 하나의 테스크를 병렬적으로 수행하는 것이죠
따라서 코어의 수만 가지고 직접적으로 비교할 수는 없습니다.
하지만 GPU의 코어의 수가 많다는 것은 어떤 테스크가 있을 때 이 일을 병렬로 수행하기 아주 적합하다는 것은 알 수 있습니다.
하지만 그 테스크는 전부 같은 테스크여야 할 것입니다.
그리고 또 한가지 말씀드릴 것은 메모리와 관련된 것입니다.
CPU에도 캐시가 있습니다. 하지만 비교적 작습니다. CPU는 대부분의 메모리를 RAM에서 끌어다 씁니다.
RAM은 일반적으로 8, 12, 16, 32 GB 바이트 정도이죠
반면 GPU는 칩 안에 RAM이 내장되어 있습니다.
실제 RAM와 GPU간의 통신은 상당한 보틀넥을 초래합니다. 그렇게 떄문에 GPU는 보통 칩에 RAM이 내장되어 있습니다.
Titan XP의 경우 내장 메모리가 12GB정도 됩니다.
GPU는 12GB의 메모리와 GPU 코어 사이의 캐싱을 하기 위한 일종의 다계층 캐싱 시스템을 가지고 있습니다.
이는 CPU의 캐싱 계층구조와 아주 유사합니다.
CPU는 범용처리에 적합합니다. CPU는 아주 다양한 일을 할 수 있죠
그리고 GPU는 병렬처리에 더 특화되어 있습니다.
GPU에서 정말 잘 동작하고 아주 적합한 알고리즘은 바로 행렬곱(Matrix multiplication) 연산입니다.
왼쪽 행렬은 행이 엄청 많은 행렬입니다. 오른쪽은 열이 많죠
오른쪽 행렬은 왼쪽 두 행렬의 내적입니다. 그리고 이 때의 내적 연산은 모두 서로 독립적입니다.
오른쪽의 결과행렬을 살펴보면 각각의 원소가 전부 독립적입니다. 따라서 모두 병렬로 수행될 수 있습니다.
그리고 각 원소들은 모두 같은 일을 수행합니다. 두개의 벡터를 내적하는 것이죠.
다만 서로 입력 데이터만 조금씩 다를 뿐입니다.
GPU는 결과 행렬의 각 요소들을 병렬로 계산할 수 있으며 이러한 특성 때문에 GPU는 엄청나게 빠릅니다.
이런 연산들은 GPU가 정말 잘 하는 것들입니다. CPU였다면 각 원소를 하나씩만 계산할 것입니다.
물론 CPU라고 그렇게 단순하게 동작하지는 않습니다. CPU는 여러개의 코어가 있고 Vectorized instructions이 존재합니다.
그렇다고 해도 아주 massive한 병렬화 문제에 대해서는 GPU의 처리량이 압도적입니다.
가령 행렬의 크기가 엄청 큰 경우가 될 수 있겠습니다. 그리고 비슷한 맥락으로 Convolution의 경우도 같습니다.
convolution에는 입력(텐서)이 있고 가중치가 있죠 cov 출력은 마찬가지로 입력과 가중치간의 내적입니다.
GPU의 경우라면 이 연산을 각 코어에 분배시켜서 아주 빠르게 연산할 수 있도록 해줍니다.
이런 종류의 연산들이 일반적으로 CPU에 비해 GPU에서 연산 속도의 이점을 볼 수 있는 것들입니다.
여러분들도 직접 GPU에서 실행되는 코드를 작성할 수 있습니다.
NVIDIA에서 CUDA를 지원하는데 그 코드를 보면 c언어 스럽게 생겼습니다. 하지만 GPU에서 실행되는 코드입니다.
하지만 CUDA코드를 작성하는 것은 상당히 까다롭습니다.
GPU의 성능을 전부 짜낼 수 있는 코드를 작성하는 것은 상당히 힘든 일입니다.
아주 세심하게 메모리구조를 관리해야 합니다. 가령 cache misses나 branch mispredictions 같은 것들을 전부 고려해야 하죠
따라서 여러분 스스로 아주 효율적인 CUDA code를 작성하는 것은 상당히 어렵습니다.
때문에 NVIDIA는 GPU에 고도로 최적화시킨 기본연산 라이브러리를 배포해 왔습니다.
가령 cuBLAS는 다양한 행렬곱을 비롯한 연산들을 제공합니다. 이는 아주 고도로 최적화되어 있습니다.
이는 GPU에서 아주 잘 동작하고 하드웨어 사용의 이론적 최대치까지 끌어올려 놓은 라이브러리입니다.
유사하게 cuDNN이라는 라이브러리도 있습니다. 이는 convolution, forward/backward pass, batch norm, rnn 등
등 딥러닝에 필요한 거의 모든 기본적인 연산들을 제공하고 있습니다.
NVIDIA는 자사의 하드웨어에 아주 효율적으로 동작하는 라이브러리를 바이어리로 배포하고 있습니다.
따라서 실제로는 딥러닝을 위해 CUDA 코드를 직접 작성하는 일은 없을 것입니다.
이미 다른 사람들이 작성한 코드를 불러와서 쓰기만 하면 됩니다.
이미 NVIDIA에서 극도로 최적화된 소스를 가져와서 쓰기만 하면 됩니다.
그리고 또 하나의 언어가 있는데 OpenCL입니다. OpenCL이 조금 더 범용적입니다.
NVIDIA GPU에서만 동작하는 것이 아니라 AMD 에서도 그리고 CPU에서도 동작합니다.
하지만 OpenCL은 아직 딥러닝에 극도로 최적화된 연산이나 라이브러리가 개발되지는 않았습니다. 그래서 CUDA보다는 성능이 떨어집니다.
미래에는 더 많은 open standard가 생기고 이종 플랫폼 간의 다양한 방법들이 생겨날지도 모르겠지만
현재로썬 NVIDIA가 딥러닝의 선두주자입니다.
GPU 프로그래밍을 직접 해보면 익힐 수 있는 다양한 리소스들이 있습니다. 아마 아주 흥미로울 것입니다.
아주 massive한 병령처리 아키텍쳐를 다루는 것이기 때문에 코드를 작성하는 패러다임도 조금 다를 것입니다.
하지만 이 이상은 우리 강의의 주제를 벗어나기 때문에 여기까지만 하겠습니다.
다시한번 만씀드리지만 GPU로의 딥러닝을 위해서 굳이 스스로 CUDA code를 작성할 필요는 없습니다.
사실 저의 경우도 연구 프로젝트를 진행하면서 CUDA code를 직접 짜본 적은 없습니다.
하지만 여러분이 직접 코드를 짜진 않더라도 어떻게 동작하는지 잘 알아두는 것은 상당히 유욜할 수 있습니다.
실제 CPU와 GPU의 성능을 한번 살펴보고 싶으시다면, 제가 작년 여름이 벤치마크 한 것이 있습니다.
그때 당시 성능이 꽤 괜찮았던 Intel CPU와 성능이 가장 좋았던 NVIDIA GPU를 비교한 것입니다.
더 자세한 내용은 제 Github에서 찾아볼 수 있습니다. VGG16/19와 다양한 ResNets을 이용했는데요
그리고 그 결과 GPU에서 보통 65에서 75배의 speed up을 볼 수 있었습니다. 완전히 같은 연산량으로 비교한 것이죠
GPU의 경우는 Pascal TitanX입니다.
CPU의 경우 최상위 성능 까지는 아니였지만 Intel E5 Processor였습니다.
하지만 여러분들이 이와 같은 딥러닝 벤치마크를 살펴볼 때 정말 조심해야 할 점이 있습니다.
왜냐하면 비교군 간에 불공평한 비교를 하기 십상이기 떄문이죠
따라서 여러분들이 벤치마크를 살펴볼 때 어떤 부분이 불공평한지를 주도면밀하게 살펴볼 필요가 있습니다.
제가 이 벤치마크에 대해 말씀드려 보자면 이 결과는 사실 CPU에게는 조금 불리합니다.
제가 CPU에서는 성능을 최대화 시킬 수 있을만큼 공을 들이기 않았기 때문입니다.
저는 아마 BLAS 라이브러리가 CPU에서 더 잘 동작하도록 수정해 볼 수도 있었을 것입니다.
그렇게 되면 이 표의 결과 보다는 조금 더 좋았을 것입니다.
하지만 이또한 뛰어난 성능이긴 합니다. Torch를 설치하고 CPU와 GPU로 실행시켜본 것입니다.
이 성능도 나쁘진 않지만 CPU의 성능을 극대화 시킨 것은 아닙니다.
CPU의 성능을 높힐 수 있는 여지는 충분했습니다.
그리고 또 하나의 벤치마크가 있는데 convolution 연산에 cuDNN을 사용한 것과 일반적인 CUDA를 사용한 것입니다.
오픈소스 커뮤니티에 게제된 결과입니다.
여기에서 볼 수 있는 것은 동일한 하드웨어와 동일한 딥러닝 프레임워크를 사용한 것이고 다른 점은 오직
cuDNN/CUDA 입니다. CUDA은 조금 덜 최적화된 것이죠. cuDNN이 3X의 속도향상이 있습니다.
일반적으로 여러분이 GPU 코드를 직접 작성해야 한다면 cuDNN을 반드시 사용해야 할 것입니다.
cuDNN 라이브러리를 쓰고 안 쓰고의 차이가 거의 3배가 날테니 말입니다.
그리고 실제로 GPU로 학습을 할 때 생기는 문제 중 하나는 바로 Model과 Model의 가중치는 전부 GPU RAM에 상주하고 있는 반면에
실제 Train data(Big data)는 SSD와 같은 하드드라이브에 있다는 것입니다.
때문에 Train time에 디스크에서 데이터를 읽어드리는 작업을 세심하게 신경쓰지 않으면 보틀넥이 발생할 수 있습니다.
GPU는 forward/backward 가 아주 빠른 것은 사실이지만, 디스크 에서 데이터를 읽어드리는 것이 보틀넥이 되는 것입니다.
이는 상당히 좋지 않은 상황이고 느려지게 될 것입니다.
해결책 중 하나는 바로 데이터셋이 작은 경우에는 전체를 RAM에 올려 놓는 것입니다.
데이터셋이 작지 않더라도, 서버에 RAM 용량이 크다면 가능할 수도 있을 것입니다.
혹은 HDD대신에 SSD를 사용하는 방법이 있습니다. 데이터를 읽는 속도를 개선시킬 수 있겠죠
또 한가지 방법은 바로 CPU의 다중스레드를 이용해서 데이터를 RAM에 미리 올려 놓는 것(pre-fetching) 입니다.
그리고 buffer에서 GPU로 데이터를 전송시키게 되면 성능향상을 기대할 수 있을 것입니다.
물론 이런 설정 자체가 조금 까다롭긴 하지만 GPU는 빠른데 데이터 전송 자체가 충분히 빠르지 못하면 보틀넥이 생길수 밖에 없습니다.
이 부분은 항상 인지하고 있어야 합니다.
지금까지는 딥러닝에 관련한 GPU와 CPU에 대한 간략한 소개였습니다.
자 이제 조금 주제를 틀어서 소프트웨어와 관련된 이야기를 해볼까 합니다.
실제로 사람들이 사용하는 딥러닝 프레임워크와 관련된 것입니다.
다음으로 넘어가기에 앞서 혹시 CPU와 GPU에 관한 질문 있으십니까?
네 질문있나요?
[학생이 질문]
질문은 바로 데이터읽기 에서의 병목을 해결하기 위해서 프로그래밍으로 우리가 할 수 있는 일이 무엇인지 입니다.
소프트웨어적으로 할 수 있는 일은 CPU에서 미리 불러오는 것입니다.(pre-fetching)
여러분은 다음과 같이 과정들이 순차적으로 진행되는 것을 원하지 않을 수 있습니다.
디스크에서 데이터를 읽습니다. 그리고 미니배치의 데이터가 다 읽힐 때 까지 기다립니다.
그리고 미니배치를 GPU에 전송합니다. 그리고 GPU에서 forward/backward를 수행하고
그다음 또 다른 미니배치를 읽는 것이죠 이 단계를 전부 순차적으로 진행합니다.
하지만 그러지 말고 CPU의 멀티스레딩을 통해 디스크에서 데이터를 불러오는 백그라운드 작업을 하고 있다면
이 과정을 interleave하게 진행할 수 있을 것입니다.
GPU이 계산을 수행하는 동안 CPU의 백그라운드 스레드는 디스크에서 데이터를 불러옵니다.
그리고 CPU의 메인 스레드에서 synchronization를 관리하게 되면 이 모든 작업이 병렬로 수행될 수 있을 것입니다.
그리고 고맙게도 여러분이 딥러닝 프레임워크를 사용한다면 이미 다 구현되어 있습니다. 일일이 구현하기 까다롭기 때문이죠
딥러닝 프레임워크의 동향을 급변하고 있습니다.
작년까지만 해도 Caffe, Torch, Tensorflow에 대해서만 언급했었습니다.
1년 전까지만 해도 Tensorflow가 가장 최신의 프레임워크라서 사람들에게 널리 쓰이지 않았었습니다.
하지만 지금은 아주 유명해져서 많은 사람들이 선택하는 메인 프레임워크가 되었습니다. 아주 큰 변화입니다.
그리고 작년부터 정말 많은 프레임워크가 생겨나고 있습니다.
특히나 Caffe2와 PyTorch가 생겼습니다.Facebook에서 밀고있는 프레임워크로 아주 흥미롭습니다.
그 밖에도 정말 많은 프레임워크들이 있습니다.
Baidu에는 Paddle이 있고 Microsoft는 CNTK이 있죠
Amazon은 주로 MXNet을 사용하는데 이 밖에도 정말 많은 프레임워크가 있습니다. 다 써볼 시간도 부족하죠
이 그림에서 주목할만한 재미있는 점은 바로 딥러닝 프레임워크의 초창기 세대는 학계(academia)에서 구축되었다는 것입니다.
원래 Caffe은 Berkeley에서 Torch는 NYU에서 개발되었고 이후 Facebook과 공동연구를 시작한 것이죠
Theana은 대부분 Montreal에서 개발했습니다.
하지만 다음세대의 딥러닝 프레임워크들은 기업(industry)에서 태동했습니다.
Caffe2와 PyTorch는 Facebook에서 나왔습니다. TensorFlow은 Google에서 나왔죠
최근 몇 년간의 흥미로운 변화는 바로 academia에서 industry로의 이동이라고 할 수 있습니다.
이제는 industry가 이러한 강력한 프레임워크를 제공하고 있습니다.
오늘은 대부분 PyTorch와 TensorFlow에 대해서 말씀드릴 것입니다. 아마 여러분은 앞으로 이 두 프레임워크를 자주 다루게 될 것입니다.
Caffe와 Caffe2도 간단히 설명해 드리겠습니다. 하지만 깊게 말씀드리진 않을 것입니다.
우선 더 깊게 들어가기 전에 제가 어떤 것을 주로 사용했는지 미리 말씀드려야 할 것 같습니다.
저 같은 경우에는 지난 몇 년간 Torch를 이용했습니다. 아주 많이 사용했고 참 좋아하는 녀석입니다.
그리고 얼마전에 PyTorch로 갈아탔습니다.
때문에 저 같은 경우 Tensorflow 같은 다른 플랫폼의 경험은 조금 부족합니다.
그렇지만 저는 여러분들에게 최대한 잘 설명해 드리기 위해 노력 할 것입니다.
자 그럼 지난 강의를 상기해보면 Computational graphs 에 대해서 수도없이 들었을 것입니다.
여러분이 딥러닝과 관련된 작업을 한다면 computational graph를 빼고는 아무 것도 할 수 없죠
여기 선형분류기의 예를 들자면 데이터 X가 있고 가중지 W가 있습니다. 그리고 이를 행렬곱으로 표현하죠
그리고 hinge loss 같은 손실함수를 계산할 것입니다.
regularization term도 있겠죠. 이런 모든 것들을 전부 그래프 구조 안에 표현할 수 있습니다.
Neural net의 경우에 이 그래프구조는 상당히 복잡합니다. 다양한 레이어와 활성함수가 존재할 것입니다.
그리고 아주 많은 가중치들이 그래프 전역에 퍼져 있을 것입니다.
Neural turing machines의 그래프는 훨씬 더 복잡합니다. 너무 크고 복잡해서 그릴 수 조차 없습니다.
따라서 우리가 굳이 코드를 손으로 작성하지 않고 딥러닝 프레임워크 를 이용하는데는 크게 세 가지 이유가 있습니다.
우선 첫째는 딥러닝 프레임워크를 이용하게 되면 이처럼 엄청 복잡한 그래프를 우리가 직접 만들지 않아도 된다는 것입니다.
두번째는, 딥러닝에서는 항상 그래디언트를 계산해야 하죠
Loss를 계산하고 Loss에 부합하는 가중치의 그래디언트를 계산해야 합니다.
그래디언트가 자동으로 계산되면 정말 좋을 것입니다. 이를 계산하는 코드를 직접 짜고싶은 사람은 없겠죠
딥러닝 프레임워크를 사용하면 forward pass만 잘 구현해 놓으면 back propagation은 알아서 구성됩니다.
그리고 마지막 세번째로는 여러분은 GPU을 효율적으로 사용하고 싶을 것입니다.
하지만 cuBLAS, cuDNN, CUDA 그리고 memory등을 여러분이 직접 세심하게 다루고 싶지는 않을 것입니다.
이런 모든 것들을 다루는 것은 정말 까다롭고 힘들 일이죠
이러한 이유 때문에 보통 밑바닥 부터 구현하지 않고 프레임워크를 이용하는 것입니다.
computational graph의 예를 한번 봅시다. 이런 식으로 엄청 간단한 예제를 만들어 볼 수 있겠죠
입력으로 X,Y,Z가 있습니다. X와 Y를 곱하면 a가 됩니다. a와 Z를 더하면 b가 되죠
그리고 b를 sum out 연산을 이용해 다 더하면 scaler 값 하나가 나옵니다. C 이죠
여러분들은 아마 지금까지의 코드를 Numpy로 충분히 구현할 수 있다고 생각하실 지도 모릅니다.
Numpy를 이용해서 random data를 입력으로 해서 적절하게 곱하고 더하고 하면 아주 쉽게 구현할 수 있겠죠
하지만 X,Y,Z에 대한 그래디언트를 구하는 경우라면 어떨까요?
여러분이 Numpy로 작성한 경우라면 backward도 여러분이 스스로 작성해야 합니다.
아마도 여러분들이 과제에서 많이 해보셨겠지만 이 복잡한 수식을 구현하는 것은 아주 까다롭고 고통스러운 일입니다.
그리고 Numpy의 또 하나의 문제점은 바로 GPU에서 돌아가지 않는다는 것입니다.
Numpy는 CPU에서만 동작하죠. 그렇게 때문에 Numpy만 쓰는 사람들은 GPU의 참맛을 경험해 보지 못합니다.
다시 말씀드리자면 그때 그때 마다 그래디언트를 여러분 스스로 계산하는 것은 상당히 힘든 일입니다.
때문에 딥러닝 프레임워크의 목표는 바로 여러분이 forward pass 코드를 Numpy 스럽게 작성을 해 놓으면
GPU에서도 동작하고 그래디언트도 알아서 계산해 주는 것입니다.
이는 프레임워크가 존재하는 가장 큰 목적입니다.
Tensorflow의 예를 한번 볼까요? 두 코드의 computational graph는 동일합니다. forward pass를 한번 봅시다.
Tensorflow 의 코드를 보면 Numpy의 forward pass 코드와 아주 유사합니다. 곱셈/덧셈 연산을 한번 보시죠
하지만 Tensorflow에는 여러분을 위해 그래디언트를 계산해 주는 마법의 코드가 있습니다.
직접 backward pass를 작성 할 필요가 없는 것입니다. 아주 유용한 기능입니다.
그리고 Tensorflow가 좋은 점 중 하나는 명령어 한 줄이면 CPU/GPU에서 동작하도록 전환 시킬 수 있다는 것입니다.
forward pass에서 이런 식으로 선언을 해주면 난 CPU로 돌리고 싶어! 라고 하는것 과 같죠
그리고 단지 'C'를 'G'로 바꾸기만 하면 이 코드는 GPU에서 동작하게 됩니다.
코드 몇 줄만 추가해 주면 두 가지 문제를 해결할 수 있는 것입니다. 우선 GPU를 사용할 수 있게 해주는 것이고
모든 그레디언트를 계산할 수도 있습니다. 아주 큰 장점입니다.
PyTorch도 거의 비슷합니다.
PyTorch의 예로 다시한번 살펴보자면 여기에 번수를 선언하고
forward pass를 진행합니다. 여기에서도 Numpy에서의 코드와 아주 유사하다는 것을 알 수 있습니다.
PyTorch에서도 한줄이면 그레디언트를 계산할 수 있습니다.
PyTorch에서 GPU를 사용하려면 CUDA data type으로 선언하기만 하면 됩니다. 그러면 GPU에서 연산이 수행되죠
지금까지 세가지 예제를 살펴보았습니다. Numpy, Tensorflow, PyTorch 입니다.
TensorFlow와 PyTorch모두 Numpy 스럽게 생겼습니다.
Numpy가 사용하기 쉽기 때문에 Numpy처럼 생긴 것은 정말 좋은 특성입니다.
Numpy스럽게 생겼을 뿐만 아니라 그레디언트도 알아서 계산해주고 GPU에서도 자동으로 돌아갑니다.
간략한 소개가 끝났으니 이제 TensorFlow에 대해서 좀더 심도깊게 알아보도록 하겠습니다.
앞으로는 이 한가지 예시를 가지고 계속 말씀드릴 것입니다. 두개의 fc layer + ReLU를 학습시키는 네트워크 입니다.
그리고 손실함수로는 L2 Euclidean 를 사용합니다.
이 네트워크의 성능을 기대하긴 어렵겠지만 딥 러닝을 프레임워크를 설명하는데는 아주 재격입니다. 슬라이드 크기에도 딱 맞죠
그리고 이 코드는 numpy와 tensorflow를 이미 import한 것입니다.
Tensorflow의 경우에는 코드가 크게 두가지 스테이지로 나뉩니다.
상단에 빨간색 박스를 보시죠. 우선은 computational graph 를 정의하는 코드를 작성할 것입니다.
그래프를 정의하고 나면 그래프를 실행시켜야겠죠
우선 연산을 수행하려면 데이터를 넣어줘야 합니다.
Tensorflow를 사용할 때 아주 일반적인 순서가 되겠습니다.
우선 그래프를 구성하는 코드를 한번 작성해 놓으면 여러분은 이 그래프를 언제든지 실행시킬 수 있습니다.
그래프를 구성하는 부분을 좀 더 자세히 살펴봅시다.
우선 맨 처음에 X,Y,w1,w2를 정의합니다. 이들은 모두 tf.placeholder 객체입니다.
이들은 그래프의 입력노드가 됩니다.
computational graph의 입구 역할을 하는 것이죠. 가령 데이터를 넣어주고자 하면 이쪽으로 넣어주면 됩니다.
하지만 이 부분에서 실제적인 메모리할당이 일어나진 않습니다. 단지 그래프만 구성하는 것입니다.
앞서 선언한 입력 변수를 이용해 봅시다.
이 변수들을 기반으로 다양한 Tensorflow 연산을 수행합니다. 여러분이 만들고 싶은 연산을 구성할 수 있는 것이죠
이 예제의 경우 X와 w1의 행렬곱 연산을 수행합니다. 그리고 tf.maximum을 이용해 ReLU를 구현할 수 있습니다.
그리고 행렬곱 연산을 한번 더 수행해서 그래프의 최종 출력값을 계산할 수 있습니다.
그리고 예측값과 정답값 Y의 유클리디안 거리를 계산하기 위해서도 Tensor operation 을 사용합니다.
한가지 주목할 점은 이 코드는 현재 아무런 연산도 수행하지 않는다는 점입니다. 아직까지는 계산할 데이터가 존재하지 않습니다.
지금은 단지 그래프 구조만 만들어 놓아서 실제 데이터가 들어왔을때 어떻게 연산을 진행해야 하는지만 구성합니다.
그래프를 구성하는 것 외에 아무 일도일어나지 않습니다.
그리고 이 빨간색 라인을 통해 로스를 계산하고 w1과 w2의 그레디언트를 계산해주는 마법적일 일을 할 수 있습니다.
이를 통해 여러분이 과제에서 했던 backporp 코드를 직접 짜는 수고를 덜 수 있습니다.
이 때도 사실은 실제 계산이 이루어지지는 않습니다.
여기에서도 단지 그래프에 그레디언트를 계산해 주는 추가적인 연산을 더해준 것과 같습니다.
자 지금까지는 computational graph를 구성했습니다. Loss와 그레디언트를 계산하기 위한 커다란 그래프를 만든 것이죠
자 이제 Tensorflow session으로 들어가 봅시다. 이를 통해 실제 그래프를 실행시키고 데이터를 넣어줄 수 있습니다.
session에 진입하려면 그래프에 들어갈 실제 값을 만들어 줘야 합니다.
대부분의 경우 TensorFlow는 Numpy arrays 이용할 수 있습니다.
이 경우에도 Numpy를 이용해서 x,w1,w2,y의 값을 할당해 줬습니다.
그리고 바로 이 부분이 실제로 그래프를 실행시키는 부분입니다.
session.run 을 이용해서 그래프의 일부를 실행시킬 수 있습니다.
첫 번째 인자가 loss라는 것은, 우리가 그래프의 출력으로 어떤 부분을 원하는 지를 말해주는 부분입니다.
예제에서는 우리가 현재 Loss와 grad1과 grad2를 계산하기를 원한다고 말해주고 있는 것입니다.
그리고 feed_dict를 통해 실제 값을 전달해 주는 것이죠
이 한 줄만 실행시키면 그래프가 실행되고 loss와 grad1과 grad2가 계산됩니다.
그리고 출력 값들도 Numpy array입니다.
output이란 변수를 나눠보게 되면 loss와 gradient가 Numpy array의 형태로 반환됨을 알 수 있습니다.
이 값을 가지고 여러분이 원하는 작업을 하시면 됩니다.
이 경우에는 그래프에서 단 한 번의 forward/backward pass 를 수행한 경우 입니다.
네트워크를 학습시키기 위해서는 단 몇 줄이면 충분합니다.
그래프를 여러번 실행시키기 위해서는 for loop를 사용하면 됩니다. 반복적으로 session.run을 호출해 loss와 grad를 계산합니다.
이 예제에서는 가중치를 업데이트하기 위해 그레디언트를 계산해서 "수동으로(manual)" Gradient diescent를 하고 있습니다.
이 코드의 loss를 계산해 보면 loss가 아주 잘 내려가며 이는 네트워크의 학습이 이루어지는 것입니다. 아주 잘 동작하는 것이죠
이 예제는 TensorFlow에서 네트워크가 어떤 식으로 학습이 되는지를 아주 명확하게 알려줍니다.
하지만 여기에는 문제가 하나 있습니다.
forward pass에서 그래프가 실행될 때마다 가중치를 넣어줘야 합니다.
현재 Numpy로 된 가중치를 가지고 있고 이 값을 그래프에 넣어줘야 하는 것이죠
그래프가 한번 실행되고 나면 그레디언트를 반환해 줬습니다. 그레디언트는 가중치와 동일한 크기의 행렬이었죠
이것이 의미하는 바는 여러분이 그래프를 실행할 때 마다 우선 Numpy array로 된 가중치행렬을 Tensorflow로 복사합니다.
그리고 gradient를 계산해서는 gradient행렬을 Tensorflow에서 Numpy array로 반환해 주는 것이죠
만약 코드가 CPU에서 실행이 되고 있다면 큰 문제가 되지는 않을 것입니다.
하지만 지난번에 GPU의 보틀넥에 대해서 말씀드린 적이 있었죠. CPU/GPU 메모리산의 데이터교환을 상당히 비용이 큽니다.
때문에 여러분의 네트워크가 엄청 크고 가중치가 엄청 많다면 이런 GPU/CPU간의 데이터 교환은 이는 엄청 느리고 비용이 클 것입니다.
이것은 안좋은 일입니다. 바꿀 필요가 있습니다.
TensorFlow에는 이에 대한 해결책이 있습니다.
굳이 매번 placeholder를 써서 가중치를 넣어 줄 필요는 없습니다. 대신에 단지 variables로 선언하는 것이죠
variable은 computational graph안에 서식하는 변수입니다. 그래프가 실행될 때 마다 지속적으로 그래프 안에 상주합니다.
따라서 w1와 w2를 placeholder 대신에 variables로 선언해 줍니다.
하지만 이제는 그들이 그래프 안에 살기 때문에 우리는 Tensorflow에게 어떻게 초기화시킬 것인지를 알려줘야 합니다.
왜냐하면 기존에 그래프 밖에 있을때는 그래프에 넣어주기 전에 Numpy를 이용해서 초기화 시켜주면 그만이었습니다.
하지만 지금은 그래프 안에 있기 때문에 그 변수들을 초기화시킬 권한은 TensorFlow에 있는 것입니다.
따라서 우리는 tr.randomnormal 이 필요합니다.
사실 저 명령어 자체가 변수들을 초기화시켜 주는 것은 아니고 Tensorflow에게 어떻게 이 값들이 초기화되야 하는지는 알려줍니다.
이 부분에서 다소 헷갈릴 수 있습니다.
자 그리고 이전 예제에서는 computational graph의 외부에서 가중치를 업데이트했습니다.
이전 예제에서는 그레디언트를 계산하고 Numpy array로 가중치를 업데이트 했죠
그리고 계산된 가중치를 다음 스텝에 다시 넣어줬습니다.
하지만 가중치를 그래프 안에서 업데이트 하기 위해서는 그 연산 자체가 그래프에 포함되어야 합니다.
이를 위해 assign 함수를 이용합니다. 이를 통해 변수가 그래프 내에서 업데이트가 일어나도록 하는 것입니다.
이 업데이트된 값들은 항상 그래프 내부에 존재하게 됩니다.
자 이제 실제 학습을 진행하기에 앞서 (tf.global_variables_initializer() 를 통해)
그래프 내부의 변수들을 초기화시켜 주는 명령어가 필요합니다.
초기화가 끝나면 이제 그래프를 돌릴 차례입니다.
이 코드를 보시면 이제는 데이터와 레이블만 넣어주면 되고 가중치는 그래프 내부에 항상 상주하게 됩니다.
여기에서는 Tensorflow에게 loss를 계산해 달라고 하는 것이죠
여러분은 이 코드로 네트워크를 학습시킬 수 있다고 생각할 수 있습니다.
하지만 사실 이 코드에는 버그가 있습니다. 이 코드를 돌려서 Loss를 그려보면 전혀 학습이 안됩니다.
도대체 왜 이런 것일까요?
assign 코드도 작성했고 그래프를 돌려서 Loss와 그레디언트도 계산을 잘 했는데 Loss가 변하질 않습니다. 왜 그럴까요?
아시는 분 계신가요?
[학생이 대답]
대답은 가중치가 매번 초기화되기 때문이라는 것인데요 그럴듯한 답변이지만 사실 그 문제는 아닙니다.
[학생이 대답]
대답은 바로 우리가 Tensorflow에게 w1과 w2를 업데이트하라고 명시적으로 말해 줘야 한다는 것입니다.
그래서 우리는이 큰 계산 그래프 데이터를 구축했습니다.
구조를 메모리에 저장하고 실행을 호출 할 때,
우리는 TensorFlow에게 우리가 손실을 계산하기를 원한다고 말했습니다.
그리고 그래프 내부의 dependence를 고려 해 보면
Loss를 계산하기 위해서 굳이 업데이트를 할 필요는 없다는 것을 알게 될 것입니다.
Tensorflow는 아주 스마트하기 때문에 output 에 필요한 연산만 수행합니다.
이는 Tensorflow의 장점이기도 합니다. 필요한 부분만 실행하기 때문이지요
하지만 간혹 이 특성이 우리를 헷갈리게 하고 예상하지 못했던 결과를 초래할 수도 있습니다.
이 경우에는 우리가 Tensorflow에게 업데이트를 수행하라고 명시적으로 말해줘야 합니다.
해결법 중 하나는 new_w1과 new_w2 를 출력으로 추가하면 됩니다.
하지만 new_w1과 new_w2의 사이즈가 큰 tensor라면은 상황이 안좋아집니다.
Tensorflow에게 출력을 요청하는 것은 매 반복마다 CPU/GPU간의 데이터 전송이 요구되기 때문입니다.
이는 좋지 않죠
이 대신에 할 수 있는 트릭이 하나 있습니다. 더미 노드(dummy node)하나를 그래프에 추가하는 것이죠
이 fake data dependencies를 이용하면 new_w1과 new_w2를 업데이트 할 수 있습니다.
그리고 그래프를 실행시키면 loss와 더미노드를 계산하게 됩니다.
이 더미노드는 아무 것도 반환하지 않지만 dependence를 만들었기 때문에 이를 통해 가중치가 업데이트 될 수 있는 것입니다.
질문있나요?
[학생이 질문]
질문은 바로 X와 Y도 그래프에 넣어주지 않은 이유는 무엇인지 입니다. 이들은 여전히 Numpy array로 되어있죠
이 예제에서는 우리가 매 반복시마다 동일한 X와 Y를 재사용 하고 있습니다.
이 경우에는 넣어줘도 되겠죠 하지만 좀 더 현실적인 경우에는 X와 Y는 미니배치입니다. 매번 바뀌기 때문에 매번 넣어줘야 합니다.
이 예제에서는 그래프 안에 넣어주는 것이 가능할 지 모르겠으나 실제로 대부분의 경우에는 그렇지 않습니다.
대부분의 경우에는 X,Y가 그래프 안에 있으면 안좋습니다.
다른 질문 있나요?
[학생이 질문]
현재 우리는 TensorFlow에게 loss와 update를 출력하길 원한다고 말했습니다.
사실 Updates는 어떤 값이 아니죠. 따라서 Updates는 None을 반환합니다.
하지만 dependence로 인해 updates를 통해 assign operations을 수행할 수 있는 것이죠
그리고 assign operations은 그래프 내부에 있기 때문에 전부 GPU 메모리 내에 상주하는 녀석들입니다.
덕분에 GPU 내부에서 업데이트를 진행하면서 이 결과를 다시 CPU로 가져오지 않아도 되도록 해 주는 것입니다.
[학생이 질문]
질문은 tf.group은 왜 none을 반환하는지 입니다.
여기에는 TensorFlow의 트릭이 숨어있습니다. tf.group자체는 어떤 복잡한 TensorFlow 객체를 반환합니다.
그래프를 구성하기 위해 필요한 일종의 TensorFlow 노드 객체를 반환하는 것이죠
그리고 session.run를 통해서 그래프를 실행하면 그때 바로 none을 반환하는 것입니다.
따라서 TensorFlow로 작업 할 때마다
그래프를 구성하는 동안 반환되는 값들은 Tensorflow 노드와 관련된 어떤 객체들이고
실제 그래프를 돌려야 실제 원하는 값들이 나오는 것입니다.
여기에서는 update를 돌리면 출력으로 none이 나오는 것입니다. 이해가 좀 되셨나요?
[학생이 질문]
질문은 바로 loss는 왜 값이 있고 updata는 none인지 입니다.
이는 바로 update가 동작하는 방식에 있습니다.
loss의 경우에는 우리가 계산하는 실제 값이 있습니다.
반면 update는 특수한 데이터 타입입니다. 실행을 해도 값을 반환하지 않습니다. none을 반환하죠
이는 일종의 TensorFlow의 마법이라고 할 수 있습니다.
아직 헷갈리면 수업이 끝나고 조금 더 말해보죠
[학생이 질문]
네 맞습니다. update의 마법은 group이라는 함수에서 비롯되는 것입니다.
assign operations를 위해서 tf.group을 이용하는 것은 사실 정상적인 방법은 아닌 것 같아 보입니다.
별로 좋은 방법은 아닌 것 같습니다. 하지만 고맙게도 Tensorflow는 이런 일들을 해주는 편리한 명령어들을 제공해 줍니다.
이는 바로 optimizer입니다.
여기 보시면 tf.train.GradientDescentOptimizer 라는 것을 사용하고 있습니다.
learning rate도 정해줍니다.
여기에 Adam이나 RMSprop같은 다양한 oprimization 알고리즘이 있는 것입니다.
자 이제 optimizer.minimize(loss) 를 호출합니다.
이는 아주 마법같은 일을 합니다.
이는 w1과 w2가 학습 가능하다는 것을 인식합니다. optimizer.minimize의 내부를 살펴보면
그래프에 w1와 w2의 그레디언트를 계산하는 노드도 추가하고
그리고 w1와 w2의 update operation도 추가합니다. 그리고 assigns을 위한 grouping operation도 있죠
이 같은 많은 일들이 내부적으로 수행됩니다.
이 함수를 들여다보면 tf.group도 들어가 있는 등 이전에 봤던 예제와 유사한 일을 하는 것입니다.
자 이제 반복문을 살펴보면 매 반복마다 loss와 update를 계산해 달라고 하고 있죠
매번 그래프에게 update를 계산하라고 할 때 마다 update가 일어납니다.
질문 있나요?
[학생이 질문]
질문은 tf.GlobalVariablesInitializer 가 무엇인지 입니다.
이는 w1과 w2를 초기화시켜 줍니다. 이 변수들을 그래프 내부에 존재하기 때문이죠
앞서 tf.variable을 만들때 tf.randomnormal이 들어갔죠 tf.GlobalVariablesInitializer이 실제 초기화를 합니다.
[학생이 질문]
잘 못들었습니다?
[학생이 질문]
placeholder는 그래프 밖에서 데이터를 넣어주는 변수이고 variable은 그래프 내부에 있는 변수입니다.
저도 자세한 것은 모르지만 코드를 살펴보거나 관련 문서를 참고하시기 바랍니다.
자 지금까지는 TensorFlow로 네트워크를 학습시키는 전체 예제를 한번 살펴보았고
이를 더 편리하게 하는 몇 가지 요소들도 살펴보았습니다.
이전 까지의 예제에서는 loss를 계산하는 연산을 우리가 직접 만들었습니다.
하지만 TensorFlow에서 제공하는 기본적인 tensor operations을 이용하는 방법도 있습니다.
Tensorflow는 일반적인 Neural network 모델에 들어가는 편리한 함수들을 제공합니다.
tf.losses.mean_squared_error를 사용할 수 있습니다.
이 tensor operations를 사용하면 L2 loss를 직접 구현하지 않아도 됩니다.
그리고 귀찮은 것이 하나 더 있습니다. 입력과 가중치를 정의하고 행렬 곱연산으로 둘을 묶는 그런 일들입니다.
이 예제에서는 bias도 넣지 않았습니다. bias를 넣을라 치면 bias를 또 초기화시켜 줘야 하고
적절한 shape으로 맞춰도 줘야 하고 또 출력 값에 맞춰서 broadcasting이 필요할 수도 있을 것입니다.
bias 하나를 추가하는데도 엄청나게 많은 코드가 필요한 것이죠
이를 일일이 다 작성하는 것은 불편합니다.
여러분이 딥러닝에 들어가는 기본적인 레이어들 가령 convolutions이나 batch norm을 구현하려 치면
입/출력도 선언해 줘야하고 이를 모두 묶어서 computational graph를 만들어줘야 하는데
이의 가중치를 초기화시켜주고 shape를 잘 맞춰주는 일은 정말로 성가신 일입니다.
이에 TensorFlow를 warpping한 higher level libraries이 존재합니다.
TensorFlow에서 제공해주는 것중 하나는 tf.layers입니다.
이 예제를 보시면 X와 Y만 placeholders로 선언해 줍니다.
그리고 밑에 줄을 보시면 h = tf.layers라고 선언합니다. 그리고 inputs = x, units = H 를 넣어줍니다.
이 또한 아주 마법스러운 것입니다.
내부적으로 w1과 b2을 variables로 만들어주고 그래프 내부에 적절한 shape으로 선언해 줍니다. 다만 우리에게는 보이지 않습니다.
그리고 xavier initialize 객체를 사용하여 어떻게 초기화시킬 것인지를 말해줍니다.
기존에는 tf.randomnormal을 사용해서 일일이 초기화시켜 줬었죠
하지만 지금은 이 모든 것들을 알아서 해줍니다. 그리고 h가 출력으로 나옵니다.
다음 레이어를 보면 이전 레이어 출력인 h를 받아서 똑같은 일을 해줍니다.
그리고 여기 activation=tf.nn.relu 를 볼 수 있는데 이를 통해 레이어에 relu를 추가해 줄 수 있습니다.
이처럼 이들은 모두 우리를 대신에서 대부분의 아키텍쳐와 관련된 세부사항들을 다뤄줍니다.
질문 있나요?
[학생이 질문]
질문은 xavier initializer이 특정 분포를 기본값으로 정해 놓고 있는지 입니다.
아마도 기본값이 있긴 할테지만 정확히는 모르겠네요 관련 문서를 한번 찾아보시기 바랍니다.
관련 문서를 찾아보는 것은 좋은 전략이 될 수 있습니다.
가중치 초기화를 더 잘해서 모델이 이전보다 더 빨리 수렴할 수도 있다면 말이죠
우리는 단지 tf.layers만 두번 호출했을 뿐인데 모델을 구출할 수 있었습니다. 세부사항을 다 기술할 필요가 없죠
이는 정말 편리합니다.
하지만 우리가 쓸 수 있는 라이브러리가 tf.contrib.layer만 있는 것은 아닙니다.
TensorFlow를 기반으로 한 아주 다양한 higher level libraries가 있습니다.
다양한 라이브러리가 존재하는 이유는 computational graph가 상대적으로 low level이기 때문입니다.
computational graph는 상대적으로 low level입니다.
반면 우리가 Neural network를 다룰때면 레이어와 가중치에 대한 개념을 다룹니다.
따라서 자연스럽게 computational graph 에 대한 조금은 더 higher level인 추상화 모델을 생각해 볼 수 있을 것입니다.
다양한 라이브러리와 패키지들은 여러분이 조금 더 높은 추상화레벨에서 작업할 수 있도록 돕는 것입니다.
아주 유명한 패키지 하나를 더 소개시켜 드리겠습니다 Keras입니다.
Keras은 아주 훌륭한 APUI로 TensorFlow를 backend로 해서 computational graph를 알아서 만들어줍니다.
Keras는 Theano backend도 지원합니다.
이 예제에서 레이어의 시퀀스로 모델을 구성하는 것을 보실 수 있습니다.
그리고 optimizer 객체를 만들고 model.compile을 하면 그래프가 알아서 만들어지는 마법이 이루어지는 것이죠
그리고 model.fit을 하게되면 전체 학습과정이 알아서 진행됩니다.
저는 더 자세한 내용은 잘 모르지만 Keras는 아주 유명하고 여러분이 TensorFlow관련된 일을 한다면 써볼만합니다.
질문있나요?
[학생이 질문]
질문은 바로 여기에는 왜 CPU/GPU에 대한 명시가 없댜는 것입니다.
제가 깔끔한 코드를 위해서 생략한 것입니다.
하지만 keras tutorial 을 살펴보시면 아시겠지만 CPU/GPU 전환은 아주 쉽습니다. global flag을 사용하거나
특정 데이터 타입을 사용하거나 with문을 이용할 수도 있는데 어쩄든 한 줄만 추가하면 될것입니다. 아주 쉽죠
다만 현재 상황에 따라 선언하는 방식은 상이할 수 있습니다.
지금 보시는 것은 실제로 여러분들이 앞으로 접하게 될 higher level TensorFlow wrappers들입니다.
심지어 Google 사람들 조차도 어떤 것이 더 좋은지 잘 모르는 것 같습니다.
Keras와 TFLearn은 third party libraries 입니다. Google이 아닌 다른 사람들이 만든 라이브러리들입니다.
그리고 여기 tf.layers, TF-Slim, tf.contrib.learn 이 있습니다.
TensorFlow와 함께 제공되는 라이브러리 입니다. 세가지 모두 다 조금씩 다른 방식으로 wrapping되어 있죠
Google에서 만들었지만 같이 제공하지는 않은 라이브러리도 있습니다. Pretty Tensor라는 것이고 비슷한 맥락입니다.
하지만 DeepMind에게는 이 모든것들이 만족스럽지 않았나 봅니다.
몇 주 전에 Sonnet이라는 자체 Tensorflow wapper를 만들었죠
더이상 나가면 여러분들이 혼란스러운 것 같아 그만하겠습니다. 결론은 여러분들에게 다양한 선택지가 있다는 것입니다.
그것 들이 상호간에 호환이 잘 안될 수는 있겠지만, 어찌됐든 여러분에게든 여러 옵션이 있는 것이니 좋은 것이죠
TensorFlow는 pretrained models를 제공합니다.
여기 주소에 TF-Slim와 Keras에서의 예제가 있습니다.
학습 시에 pretrained models이 중요하다는 것은 여러분도 잘 하실 것입니다.
Tensorboard라는 것도 있습니다. 자세히 들어가진 않겠지만
Tensorflow를 사용하게 되면 Training 하는 동안 Loss와 같은 것들을 plot할 수 있습니다.
Tensorflow는 분산처리도 지원하기 때문에 서로 다른 머신을 이용해 graph를 쪼개서 실행시킬 수도 있습니다.
Google 외에 이 기능을 잘 활용하는 사람들을 본 적은 없지만
만일 여러분이 분산처리를 계획하고 있다면 Tensorflow가 유일한 선택지가 될 것입니다.
그리고 추가로, Tensorflow는 Montreal에서 만든 Theano 라는 프레임워크의 영감을 받았습니다.
여기서 더 자세히는 설명히 드리지 않겠지만 여러분이 집에가서 이 슬라이드를 다시 한번 살펴 보시게 되면
Theano로 작성된 코드가 TensorFlow와 아주 유사하다는 것을 아실 수 있을 것입니다.
변수를 정의하고 forward pass를 수행하고 그레디언트를 계산합니다.
그리고 함수 몇개를 컴파일하고 네트워크를 계속 돌리는 것이죠
이 모든 과정이 TensorFlow와 아주 유사합니다.
아직 갈 길이 멀군요 PyTorch로 넘어갔다가 질문을 받고 수업을 끝내도록 하겠습니다.
Facebook에서 나온 PyTorch는 TensorFlow와는 다릅니다. 세 가지 추상화 레벨을 정의해 놓았죠
PyTorch에는 tensor object가 있습니다. Numpy array와 유사하죠
Tensor는 명령형(imperative) 배열이고 GPU에서 수행될 수 있습니다.
그리고 variable객체는 그래프의 노드라고 할 수 있습니다. 그래프를 구성하고 그레디언트 등을 계산할 수 있습니다.
그리고 module 객체를 이용해서 Neural network를 구성할 수 있습니다.
PyTorch와 TensorFlow를 간단히 비교해 보자면 PyTorch 의 Tensor는 Tensorflow의 Numpy array와 같은 역할이죠
PyTorch의 variable는 Tensorflow의 tensor, variavle 또는 placeholder와 같습니다.
PyTorch의 module은 tf.slim이나 tf.layers 혹은 그 밖에 다양한 higher level 프레임워크와 유사합니다.
PyTorch에 대해 한가지 명심해야 할 점은 PyTorch가 고수준의 추상화를 이미 내장하고 있습니다.
바로 modules 객체가 그것인데, 때문에 Tensorflow처럼 어떤 모듈을 선택할 지 고민할 필요가 없습니다.
그런 고민 없이 module 객체만 사용하면 됩니다.
앞서 말씀 드렸듯 PyTorch의 tensor는 Numpy array와 유사합니다.
여기 오른쪽 예제를 보시면 PyTorch tensor로 구성한 2-layer 네트워크를 보실 수 있습니다.
하지만 실제 Numpy array를 사용하지는 않고 PyTorch tensor를 이용합니다.
이 코드는 Pytorch tensor로 짜여 졌음에도 불구하고 여러분이 첫 과제에서 했던 Numpy 코드와 아주 유사합니다.
위에서 Random data를 선언하고 forward pass 를 계산하는 연산을 이용합니다.
그리고 여기에서는 backward pass도 직접 구현했습니다.
네트워크의 backprop을 구현하는 것을 과제 1에서 여러분들은 다 해 보셨을 것입니다.
그레디언트를 계산하고 Learning rate를 이용해서 가중치를 직접(manual) 계산했습니다.
다만 PyTorch tensor와 Numpy의 가장 큰 차이점이 있다면 PyTorch tensor는 GPU에서도 돌아간다는 것입니다.
이 코드를 GPU에서 실행시키려면 data type만 조금 변경해주면 됩니다.
FloatTensor 대신에 cuda.FloatTensor을 사용하면 모든 것들이 마법처럼 GPU에서 실행됩니다.
PyTorch tensor를 쉽게 생각해서 Numpy + GPU 라고 보시면 되겠습니다.
딥러닝과 직접적으로 맞닿아 있는 것은 아닙니다.
다음은 variable에 대해 알아보겠습니다.
variables은 computational graphs를 만들고 이를 통해 그레디언트를 자동으로 계산하는 등의 목적으로 이용합니다.
X는 variable이고 x.data는 tensor 입니다. x.grad도 variable인데 Loss에 대한 그레디언트를 담고 있습니다.
따라서 x.grad.data가 실제 tensor이고 이 안에 그레디언트가 담겨 있습니다.
그리고 PyTorch의 tensors 와 variables는 같은 API를 공유합니다.
PyTorch tensors로 동작하는 모든 코드는 variables로도 만들 수 있습니다.
그렇게 되면 imperative한 연산자들이 수행되는 것이 아니라 Computational graph를 만드는 것이 됩니다.
따라서 여기 보시면 variables를 선언할 때 해당 variables에 대한 그레디언트를 계산할 것인지를 지정해 줄 수 있습니다.
그리고 forward pass의 경우를 보면 tensor를 사용했을 때와 완전히 같은 코드입니다. 왜냐하면 이 둘은 같은 API이기 떄문이죠
예측값(y_pred)와 손실(loss)를 계산할 때 이런 식으로 imperative한 방법을 사용할 수 있습니다.
그리고 loss.backwards 를 호출하게 되면 그레디언트가 알아서 반환이 됩니다.
그리고 w1.grad.data의 값을 이용해서 가중치 업데이트를 할 수 있습니다.
그레디언트가 자동으로 계산된다는 것 이외에는 Numpy와 아주 유사합니다.
그렇다면 Tensorflow와 PyTorch의 차이는 무엇일까요?
TensorFlow에 경우에는 그래프를 "명시적으로" 구성한 다음에 그 그래프를 돌렸습니다.
PyTorch의 경우에는 forward pass 할 때 마다 매번 그래프를 다시 구성합니다.
그때문에 코드가 더 깔끔해 보이기도 합니다. 이에 대해 조금 더 알아보도록 하겠습니다.
PyTorch에서는 여러분만의 새로운 "자동으로 그레디언트를 계산하는(autograd)" 함수를 정의할 수 있습니다.
tensors의 형태로 forward와 backward를 정의해주면 됩니다.
이는 여러분이 과제2에서 수행했던 모듈 레이어와 비슷하게 생겼습니다.
여러분이 tensor operations을 이용해서 forward/ backward 만 구현하면 그래프에 넣을 수 있습니다.
여기 예제에서 ReLU를 직접 한번 구현해 봤습니다. 이런 식으로 직접 구현하게 되면
이 ReLU를 computational graph에 적용할 수 있습니다.
하지만 대부분의 경우에 이렇게 직접 autograd를 구현할 필요는 없을 것입니다.
대부분의 경우에 여러분이 필요한 연산들은 이미 구현이 되어있기 때문입니다.
Tensorflow의 경우에는 Keras나 TF.Learn과 같은 라이브러리가 higher level API 를 제공해 주었습니다.
PyTorch에서는 그 역할을 "nn package"가 담당합니다. high level wrappers를 제공해 줍니다.
다만 TensorFlow에는 종류가 다양했죠 하지만 PyTorch에서는 단 하나만 있는데 아주 잘 동작하고 쓸만합니다.
Linear/ReLU layer를 model sequence에 추가하는 부분은 Keras와 유사하게 생겼습니다.
그리고 바로 밑에는 nn package에서 제공하는 손실함수를 정의해 줍니다. 여기에서는 mean squared error loss 이군요
그리고 매 반복마다 forward pass를 수행하여 prediction 결과를 얻습니다.(첫 번째 라인)
그리고 손실함수를 실행하여 Loss도 구해줍니다. (두 번째 라인)
그리고 loss.backward를 호출하면 매 반복시 마다 그래디언트가 저절로 계산이 됩니다.
그리고 모델 업데이트를 위해 gradient descent step을 명시적으로 수행시켜 줍니다.
앞서 말씀드렸듯 PyTorch는 forward pass 할 때 마다 매번 새로운 computational graph를 만들어 주는 것입니다.
TensorFlow에서 처럼 PyTorch도 optimizer operations를 제공합니다.
가중치 업데이트 부분을 추상화시켜서 Adam 과 같은 알고리즘을 더 쉽게 쓸 수 있습니다.
이런 식으로 optimizer 객체를 구성해 놓는 것은 모델에게 파라미터를 optimize하고 싶다고 말해 주는 것입니다.
learning rate같은 하이퍼 파라미터도 정해줘야 합니다.
그리고 그레디언트를 계산하고 난 후에 optimizer.step를 호출하게 되면 모델 파라미터가 업데이트 됩니다.
PyTorch를 사용할 때 여러분들이 아주 흔하게 접하게 될 것은 바로 여러분만의 nn modules을 정의하는 것입니다.
여러분의 전체 네트워크 모델이 정의되어 있는 class를 nn module class로 작성해야만 합니다.
module은 일종의 네트워크 레이어라고 보시면 됩니다. 다른 module이 포함될 수도 있고 학습가능한 가중치도 포함이 될 수 있죠
여기 2-Layer Network 예제를 다시한번 보겠습니다. nn module class로 작성이 되어있는 예제입니다.
이 클래스의 생성자를 보면 linear1과 linear2가 선언이 되어 있습니다.
이 두 개의 module objects를 여러분의 클래스 안에 저장하고 있는 것입니다.
그리고 forward pass에서는 네트워크 출력을 계산하기 위해 앞서 정의한 모듈도 사용할 수 있고 다양한 autograd도 사용할 수 있습니다.
forward함수 내부를 한번 들여다보면 입력 x는 variable입니다.
입력 x가 첫 번째 레이어인 self.linear 1을 통과합니다.
그리고 autograd op을 사용해서 relu를 계산합니다.
그리고 그 출력 값이 linear2를 통과해서 다시 값을 출력합니다.
그리고 이 밑의 나머지 코드는 이전과 동일합니다.
optimizer를 구성하고 반복문을 돌면서 데이터를 넣어주고 backwards로 그래디언트를 구하고 step으로 업데이트하죠
이 예제가 PyTorch로 학습을 하는 경우 가장 일반적인 패턴입니다.
우선 모델을 구성하는 클래스를 정의하고 반복문을 돌면서 이 모델을 업데이트 하는 것입니다.
PyTorch에는 dataloader가 아주 유용합니다. dataloader는 여러분을 위해 minibatches를 관리하죠
학습 도중 Disk에서 minibatches를 가져오는 일련의 작업들을 multi-threading를 통해 알아서 관리해 줍니다.
여기 처럼 dataloader는 dataset를 wrapping하는 일종의 추상화 객체를 제공해 줍니다.
실제로 여러분의 데이터를 이용하고자 할 때 데이터를 어떤 방식으로 읽을 것이지를 명시하는 dataset class만 작성해 준다면
이 class를 dataloader로 wrapping 시켜서 학습을 시킬 수 있을 것입니다.
여기 보시면 dataloader 객체를 순회하면서 매 반복시 마다 데이터의 minibatch를 적절하게 반환시켜 줍니다.
그리고 내부적으로 data shuffling이나 multithreaded dataloading와 같은 것들을 알아서 관리해 주게 됩니다.
지금 보시는 예제가 여러분이 PyTorch 를 사용할 때 가장 많이 보게 될 코드패턴이라고 보시면 되겠습니다.
PyTorch는 pretrained models를 제공합니다.
제가 본것 중에서 PyTorch의 pretrained model이 가장 간드러졌습니다.
torchvision.models.alexnet(pretained=true) 라고만 쓰면 끝입니다.
이 코드는 여러분이 이 가중치를 처음 사용하는 경우라면 저절로 pretrained models을 다운로드하게 해줍니다.
사용하기 아주 쉽습니다.
PyTorch는 또한 Visdom라는 패키지를 제공합니다.
Tensorboard와 유사하게 Loss에 대한 통계같은 것들을 시각화해주는 패키지입니다.
아주 좋은 패키지이긴 하나 제가 직접 사용해볼 기회가 없어서 Visdom이 얼마나 유용한지 말씀드릴 수는 없을 것 같습니다.
다만 Tensorboard와 Visdom의 차이가 있다면 Tensorboard 의 경우에 computational graph의 시각화를 제공합니다.
이는 아주 유용한 debugging strategy가 될 수 있죠 하지만 Visdom는 이 기능을 제공하지 않습니다.
하지만 제가 Visdom을 직접 써보지 않아서 활요에 대해 심도깊게 말씀드리기 어려울 것 같습니다.
어쨋든, PyTorch는 제가 지난 수년간 사용했던 Torch 라는 프레임워크가 새롭게 진화된 프레임워크입니다.
깊게 말씀드리진 않겠지만 PyTorch가 기존의 Lua Torch보다 훨씬 더 좋은 것은 사실입니다만
사실 이 둘 모두 동일한 C code Backend를 가지고 있습니다. tensors를 계산하거나 GPU 연산을 하는 등에 있어서 말이죠
여기 Torch 예제를 보시면 일부는 PyTorch와 유사하고 또 일부는 조금 다른 부분도 있습니다.
집에 가서 한번 살펴보시기 바랍니다.
Torch와 PyTorch의 주된 차이점을 한번 비교해 보자면 Torch는 Python이 아닌 Lua로 작성되어 있습니다.
Lua를 배우는 것은 일부 사람들에게 있어서는 시간낭비 일 수도 있을 것입니다.
Torch는 autograd도 제공하지 않습니다.
하지만 Torch가 더 오래되었기 때문에 안정성이 조금 더 보장되고 버그도 적습니다. 그리고 Torch의 예제코드가 더 많죠
이 둘의 연산 속도도 거의 비슷합니다.
반면 PyTorch는 Python으로 작성할 수 있으며 autograd도 지원합니다. 복잡한 모델을 더 쉽게 다룰 수 있겠지요
Lua Torch는 대부분의 경우 backporp 코드를 직접 작성해야 합니다. 아주 귀찮은 일입니다.
하지만 PyTorch가 더 최신에 나왔기 때문에 예제 코드가 더 적습니다. 따라서 PyTorch를 사용하는 것이 좀 더 모험적인 일이 될 수 있습니다.
하지만 저같은 경우에는 굳이 PyTorch를 두고 Torch를 써야만 하는 이유를 못느끼기 때문에 현재는 거의 PyTorch만 사용하고 있습니다.
앞서 static graphs VS dynamic graphs 에 대해서 말씀드린 적이 있었죠
이는 PyTorch와 TensorFlow의 주된 차이점 중 하나입니다.
TF는 두 단계로 나뉩니다. 첫째는 그래프를 구성하는 단계입니다. 그리고 두번째는 이 그래프를 반복적으로 돌리는 단계입니다.
이를 static computational graph라고 합니다. 그래프가 단 하나만 고정적으로 존재하기 때문이죠
PyTorch는 완전히 다른 방식입니다. 매번 forward pass할 때 마다 새로운 그래프를 구성합니다.
이를 dynamic computational graph라고 합니다.
이런 단순한 feed forward neural network의 경우에는 별로 큰 차이점이 없어 보일 수도 있습니다.
코드도 비슷하게 생겼고 동작도 아주 비슷하죠
하지만 static과 dynamic간의 서로 다른 특징과 trade-off에 대해서 조금 더 말씀드리고자 합니다.
static graphs의 관점에서 보면 사실 그래프를 한번 구성해 놓으면 학습시에 똑같은 그래프를 아주 많이 재사용하게 됩니다.
static graphs에서는 그 그래프를 최적화시킬 기회가 주어질 수 있는 것입니다.
일부 연산들을 합쳐버리고 재배열시키는 등으로 가장 효율적으로 연산을 하도록 최적화 시킬 수 있는 것이죠. 아주 효율적인 방법입니다.
그래프 하나만 하지고 아주 여러번 사용할 것이기 때문에 처음의 최적화작업 자체가 조금 오래 걸릴순 있어도
최적화된 그래프를 여러번 사용한다는 것을 고려해보면 최적화에 소요되는 시간은 아무것도 아닐 수 있습니다.
조금 더 구체적인 예를 들어보자면 여기 Conv와 ReLU가 반복되는 모델이 있다고 해봅시다.
이 그래프를 최적화 한다고 했을때 Conv와 ReLU를 합쳐버리는 것을 생각해 볼 수 있을 것입니다.
어짜피 합쳐도 같은 연산을 하는 것이고 대신 코드는 더 효율적으로 실행될 수 있을 것입니다.
저도 TensorFlow graph optimization가 실제로 어떤 방식으로 동작하는지 확실하게 알지는 못하지만
하지만 확실한 것은 static graphs에서는 그래프를 최적화시킬 수 있는 여지가 주어진다는 것입니다.
dynamic graphs 에서는 그래프 최적화를 다루기 어려울 것입니다.
static vs dynamic의 또 한가지 차이점은 serialization에 관한 것입니다.
static graph 를 사용하여 그래프를 구성한다고 해봅시다
그래프를 한번 구성해 놓으면 메모리 내에 그 네트워크 구조를 가지고 있는 것입니다.
그렇게 되면 그 자체를 Disk에 저장할 수 있을 것입니다.
이렇게 되면 전체 네트워크 구조를 파일 형태로 저장할 수 있는 것입니다.
그러고 나면, 원본 코드 없이도 그래프를 다시 불러올 수 있습니다. 이는 아주 좋은 특성입니다.
가령 여러분은 네트워크를 Python으로 학습시키고 싶을 것입니다. Python이 쉽기 때문이죠
그리고 이를 serialize하면, C++환경에서 아주 쉽게 동작시킬 수 있습니다. 그래프를 구성하는데 기존의 코드는 필요하지 않습니다.
이는 static graphs의 아주 좋은 이점이라고 할 수 있습니다.
반면에 dynamic graph의 경우에는 "그래프 구성" 과 "그래프 실행" 하는 과정이 얽혀 있기 때문에(interleaving)
모델을 재사용하기 위해서는 항상 원본 코드가 필요합니다.
하지만 dynamic graphs의 장점은 대다수의 경우에 코드가 훨씬 더 깔끔하고 작성하기 더 쉽습니다.
가령 조건부 연산(if 문)을 해야하는 상황을 가정해 봅시다. 변수 z의 값에 따라서 y값이 달라지는 경우입니다.
Z가 양수면 w1*x 연산을, 음수면 w2*x 연산을 수행해야 하죠
조건부 연산을 사용해서 이 두 가지 연산을 오가야 하는 상황입니다.
PyTorch에서는 dynamic graphs를 사용하므로 이 문제를 다루기 아주 수월합니다.
Numpy와 아주 유사합니다. 이 문제를 다루데는 일반적인 Python if문을 사용하면 그만이죠
dynamic graphs의 경우에는 매번 새로운 그래프를 구성하기 때문에
매번 이 두 가지 선택지 중에 현재 forward pass에 적절한 하나를 선택해서 새로운 그래프를 만들어주면 그만입니다.
새롭게 구성된 그래프로 backporb에도 아무 문제가 없겠죠
코드를 아주 깔끔하게 짤 수 있습니다.
TensorFlow에서는 조건부 연산을 넣기 조금 더 복잡합니다. 왜냐하면 우선 그래프를 하나 만들어 놔야 합니다.
그래프 내에 조건부 연산을 명시적으로 정의하는 Control flow operator를 추가해야만 합니다.
Tensorflow의 예제에서는 tf.cond라는 것을 보실 수 있습니다. Tensorflow버전의 if문 이라고 보시면 됩니다.
Python의 if문을 쓰는 것 대신에 이런 식으로 그래프 내에 control flow자체를 넣어줘야 하는 것입니다.
Tensorflow에서는 그래프가 단 한번만 만들어지기 때문에 가능한 모든 control flow를 미리 고려해서
그래프 내에 한번에 넣어줘야만 합니다. 그래프를 실제로 실행시키기 전에 말이죠
Control flow를 위해서는 간단한 python 문법으론 불가능하고 반드시 특수한 Tensor Flow 연산자가 필요하다는 것입니다.
지금 예제의 경우에는 tf.cond이 되겠습니다.
조건부 연산과 유사한 상황이 반복연산(loop) 에서도 발생합니다.
가령 어떤 재귀적인 연산을 한다고 해봅시다. Y_t = (Y_t-1 + X) * W 같은 경우죠
이런 연산을 수행할 때 우리의 데이터 sequence는 다양한 사이즈일 수 있습니다.
데이터의 sequence 길이가 얼마인지 신경쓰지 않고 재귀연산을 할 수 있다면 좋겠죠
PyTorch의 경우에 엄청나게 간단합니다. Python에서 제공하는 기본 for loop를 이용하면 끝입니다.
적절한 Loop만 돌면 됩니다. 이를 통해 데이터 사이즈에 맞는 적절한 그래프를 손쉽게 만들 수 있습니다.
그리고 이는 backprob에도 지장이 없습니다.
하지만 TensorFlow에서는 이 과정이 아주 지저분합니다.
Tensorflow에서는 그래프를 앞에서 미리 만들어줘야 하기 때문에 그래프에 명시적으로 loop를 넣어줘야만 합니다.
여러분이 함수형 프로그래밍을 잊지 않고있길 바랍니다. 왜냐하면 TensorFlow에서 looping을 구현하려면 반드시 필요하기 때문이죠
이 경우에 특정 재귀적인 관계를 정의하기 위해서는 tf.foldl연산을 사용해서 구현해볼 수 있습니다.
TensorFlow에 대해 기본적으로 말씀드리고 싶었던 것은 TensorFlow를 사용하게 되면 여러분들은 반드시
computational graphs의 "전체 흐름" 을 여러분들이 전부 다 구성해 놔야 한다는 것입니다.
그래프에 필요한 모든 control flow연산 그리고 모든 데이터 구조 등을 전부다 구현해 놔야 합니다.
따라서 Tensorflow에서는 Python의 명령어들을 같이 활용할 여지가 별로 없습니다.
TF에서 필요한 control flow 연산들을 전부 다시 배워야만 합니다. TensorFlow에서 그것을 사용해야 한다면 말이죠.
적어도 저의 경우에는 Tenforflow를 사용할 때 상당히 혼란스러웠습니다.
반면에 PyTorch의 dynamic graphs를 사용하는 경우라면 다양한 Python 명령어들을 이용할 수 있습니다.
사실 TensorFlow Fold라는 TF 라이브러리가 있습니다. TensorFlow에서 dynamic graphs를 작성하게 해줍니다.
TensorFlow Fold가 dynamic graph를 만들어주는 것 처럼 보이지만 사실 static graph로 만든 트릭입니다.
TF Fold는 이번주에 ICLR in France에서 발표된 아주 최신의 연구(논문)입니다.
그래서 아직 TF-Fold를 써보지는 못했습니다.
하지만 제 첫인상으로는 TF-Fold를 통해 TensorFlow로 dynamic graphs 역할을 더해줄 순 있으나
아직까지는 PyTorch같은 네이티브 dynamic graphs 에 비하면 많이 어색해 보였습니다.
그렇다면 어떤 상황에서 dynamic graph의 사용을 고려해 볼만 한 것일까요?
그중 하나는 바로 recurrent networks 의 경우입니다.
image captioning의 경우가 그렇습니다. 이는 다양한 길이의 sequences를 다루기 위해 RNN을 이용합니다.
이 예시의 경우에는 우리가 만들어내고자 하는 caption이 바로 sequence입니다.
이 caption이라는 sequence는 입력 데이터에 따라서 다양하게 변할 수 있습니다.
이 문제의 경우에는 dynamism을 가지고 있습니다. sentence의 크기에 따라서 -
computational graph가 더 커질 수도, 작아질 수도 있을 것입니다.
Image caption은 dynamic graphs를 이용하는 가장 일반적인 예시 중 하나입니다.
지난 학기에 CS224N를 수강하신 분들이라면 recursive networks에 대해서 들어본 적이 있으실 것입니다.
가령 자연어 처리 분야에서 문장을 파싱하는 문제에서 트리를 파싱하기 위해 recursive한 네트워크가 필요할 수 있습니다.
이런 문제의 경우 layer의 "sequence 구조"를 이용하기 보다는 "graph 나 tree 구조" 를 이용합니다.
데이터에 따라 다양한 graph 또는 tree 구조를 가질 수 있습니다.
따라서 그래프를 구성할 때 데이터의 구조를 반영하도록 구성하면 다양한 데이터에도 유동적인 네트워크를 만들 수 있을 것입니다.
이런 네트워크를 구성하려면 Tensorflow에서는 정말 복잡하고 까다로울 수 있습니다.
하지만 PyTorch를 사용하게 되면 기존의 Python control flow 만 사용하더라도 충분합니다.
VQS를 다루는 Neuromodule라는 연구가 있습니다. 아주 멋진 아이디어입니다.
"이미지"와 "질문"을 던지면 적절한 답을 하는 것이죠. 이 이미지에는 고양이와 강아지가 있군요
"고양이의 색은?" 과 같은 질문을 던지면 이 문제를 풀기에 적합한 네트워크 를 구성합니다. 여기에선 "색" 과 "고양이" 를 찾게 되겠죠
질문이 주어지면 그 질문에 답하기 위한 적절한 네트워크 (custom)를 구성하는 것입니다.
"고양이가 강아지보다 많은지?" 와 같은 질문을 한다면 어떻까요?
이 경우에도 이전과 같이 "고양이"와 "강아지" 를 찾는 네트워크가 들어갈 테지만 조금은 구성이 달라질 것입니다.
여기에서 dynamism이 적용될 것입니다. 문제가 달라지면 computation graph도 다르게 구성되겠죠
아직 연구가 진행 중인 분야이며 아직 까지는 main stream은 아닙니다.
가장 중요하게 말씀드리고 싶은 것은 dynamic graph를 이용한다면 정말 많은 것들을 할 수 있다는 것입니다.
아직은 Dynamic 그래프를 다루는 것이 쉽지 않기 때문에 관련 연구가 그렇게 많지는 않습니다.
Dynamic computrational graphs를 사용한다면 아주 다양한 대단하고 창의적인 것들을 만들어 낼 수 있을 것이라 생각합니다.
여러분들이 좋은 아이디어를 만들어 내신다면 제가 내년 이 강의해서 소개해 드리도록 하겠습니다.
자 이제 Caffe에 대해서 간단하게 말씀드려보겠습니다. Berkeley에서 처음 만든 프레임워크입니다.
Caffe는 다른 딥러닝프레임워크랑은 다소 다릅니다. 많은 경우에 코드를 작성하지 않아도 네트워크를 학습시킬 수 있죠
기존에 빌드된 바이너리를 호출하고 Configuration 파일만 조금 손보면
굳이 코드를 작성하지 않아도 데이터를 학습시킬 수 있습니다.
우선 데이터를 HDF5 또는 LMDB포맷으로 변환시켜 줍니다.
이는 Caffe에서 지원하는 스크립트를 이용하면 됩니다. 폴더안에 저장된 이미지를 적절한 포맷으로 변환시켜줍니다.
Caffe에서는 그래프를 구성하려면 코드를 작성하는 대신에 prototxt라는 텍스트파일을 만들어야 합니다.
여기 구조를 보시면 입력을 HDF5 파일로 받는 부분도 있고 내적을 하는 부분도 있습니다. 이런 형식으로 그래프를 구성합니다.
이런 방식은 네트워크의 규모가 커지면 상당히 보기 안좋다는 단점이 있습니다.
가령 152레이어의 ResNet 모델을 Caffe로 훈련시키려 하면 prototxt 파일은 거의 7000줄에 이르게 됩니다.
그래서 사람들은 이것을 손으로 직접 작성하기보다는 prototxt 파일을 생성하는 Python 스크립트를 작성하곤 합니다.
[웃음]
Caffe에서는 그래프를 하나하나 전부 다 기술해 줘야 합니다. 그닥 좋은 방법은 아닌 것 같아 보이지만 말이죠
그리고 Caffe는 optimizer나 solver를 정의하기 위한 또 다른 prototxt 파일을 작성해야 합니다.
그 곳에 learning rate와 optimiation algorithm 같은 것들을 정의해 줍니다.
이런 식의 작업을 모두 마치고 Caffe의 바이너리만 실행시키면 학습이 자동으로 수행됩니다.
Caffe에도 아주 다양한 pretrained model을 제공하는 model zoo가있습니다. 아주 유용하죠
그리고 Caffe는 Python 인터페이스를 지원하긴 하지만 문서화가 잘 되어있는 편은 아닙니다.
python 인터페이스와 관련된 소스코드도 있긴 하지만 상당히 불편합니다. 그렇긴 해도 동작은 합니다.
Caffe의 경우에는 feed forward 모델에 적합한 편입니다. 그리고 production의 측면에 적합하다고 볼 수 있습니다.
왜냐하면 Python에 의존적이지 않기 때문이죠
하지만 오늘날 Caffe는 research의 목적으로는 잘 사용하지 않습니다.
하지만 Caffe는 industry에서 production 개발을 위해서 여전히 많이 사용합니다.
한 두개의 슬라이드로 Caffe2를 간단히 설명드리겠습니다.
Caffe 2를 Caffe의 다음 버전입니다. Facebook에서 만들었죠
지난 주에 배포된 아주 따끈한 신상이죠 (2017년 4월 27일 기준)
[웃음]
Caffe2를 유심히 살펴볼 새가 없었습니다.
Caffe2도 TensorFlow처럼 Static graph를 사용합니다.
Caffe처럼 코어는 C++로 작성되어있고 Python 인터페이스도 제공합니다.
Caffe1과 다른점이 있다면 더이상 prototxt 파일을 만들기 위해 Python 스크립트를 작성할 필요가 없다는 것입니다.
Tensorflow 스럽게 Python으로 그래프를 작성할 수 있습니다.
그리고 이 그래프를 prototxt 파일로 변환시켜 줄 수 있습니다.
모델을 한번 훈련시켜 놓게 되면 앞서 Static 그래프의 이점에 대해서 말씀드렸듯이
기존의 소스코드 없이도 학습시킨 모델을 쓸 수 있는 것입니다.
한 가지 흥미로운 점은 Google의 경우에는 주력 딥러닝 프레임워크가 TensorFlow 하나 뿐입니다. 하지만 -
Facebook의 경우에는 두 개죠 PyTorch와 Caffe2 입니다.
이 차이는 서로 다른 철학에서 비롯됩니다.
Google의 경우에는 딥러닝이 필요한 모든 곳에서 동작하는 프레임워크를 만들고싶어 합니다.
그래서 통합된 프레임워크 하나를 만들고 그곳에 올인하는 것이죠
TF하나로 distributed systems, production, deployment, mobile, research를 다 커버하도록 말이죠
이 모든 것을 다루는데 프레임워크 하나면 충분합니다.
하지만 Facebook의 생각은 조금 다릅니다.
PyTorch는 연구(research)에 특화되어 있습니다. 연구 사이클을 상당히 단축하도록 만들어졌습니다.
연구에는 PyTorch가 쉽지만 제품(production)개발을 위한 지원이 그닥 많지 않습니다.
대신 Caffe2가 그 역할을 담당하는 것입니다.
그렇다면 어떤 프레임워크를 어떻게 사용해야할지에 대한 제 주관적인 의견을 말씀드리자면
Tensorflow 같은 경우에는 여러분이 어떤 프로젝트를 하던 상당히 괜찮은 선택입니다.
Tensorflow는어떤 환경에서든 잘 동작하는 만능 프레임워크이기 때문입니다.
하지만 여러 higher level wrapper를 섞어 써야 하거나 Dynamic graph가 필요한 경우 곤란해 질 수도 있습니다.
Tensorflow의 Dynamic 그래프 코드가 조금 지저분하기 떄문이죠 물론 제 개인적인 의견이고, 사실 큰 문제도 아닙니다.
저는 개인적으로 PyTorch가 research에는 아주 유용하다고 생각합니다.
연구목적으로만 사용한다면 PyTorch가 최고의 선택이 될 수 있습니다.
하지만 나온지 얼마 안되서 커뮤니티 규모가 작고 찾을만한 코드도 적습니다. 모험을 해야할 수도 있습니다.
이미 사람들이 잘 다져놓은 길을 가고 싶다면 Tensorflow가 더 좋은 선택일 수 있습니다.
만약 여러분이 제품을 배포하려는 목적이라면 Caffe, Caffe2 또는 Tensorflow를 선택해야 합니다.
또한 특히 모바일 디바이스라면 Tensorflow와 Caffe2가 지원하는 것으로 알고 있습니다.
불행하게도 무조건 이 프레임워크가 최고다 라는 것은 없습니다. 선택은 여러분의 문제의 성격에 달려있는 것입니다.
앞서 말씀드린 내용들을 기준들은 제가 일반적으로 조언해 드리는 내용입니다.
다음 강의에서는 다양한 CNN 아키텍쳐에 대해서 배워보도록 하겠습니다.